using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics.Contracts;

namespace FoundationLib.Mail 
{
	/// <summary>
	/// This class represents an abstract superclass for mail templates.
	/// </summary>
	public abstract class MailTemplate : IMailTemplate 
	{
		/// <summary>
		/// The mail connector
		/// </summary>
		private IMailConnector connector;
		/// <summary>
		/// The template subject
		/// </summary>
		private string subject;
		/// <summary>
		/// The template contents
		/// </summary>
		private string contents;
		/// <summary>
		/// The template mail data
		/// </summary>
		private MailData data;
		/// <summary>
		/// The template parser
		/// </summary>
		private TemplateParser parser;
		/// <summary>
		/// A value indicating whether template processing should be aborted.
		/// This flag can be set using the <see cref="Abort"/> method and is 
		/// reset automatically by <see cref="Process()"/>.
		/// </summary>
		private bool abort;
		/// <summary>
		/// A list of email recipients
		/// </summary>
		private IList<string> mailRecipients;


		/// <summary>
		/// Creates a new mail template instance.
		/// </summary>
		protected MailTemplate()
		{
			this.data = new MailData();
			this.abort = false;
			this.mailRecipients = new List<string>();
		}

		/// <summary>
		/// Gets or sets the mail connector to be used while processing this template.
		/// </summary>
		/// <exception cref="ArgumentNullException">A null-reference was passed.</exception>
		public virtual IMailConnector Connector
		{
			get
			{
				return this.connector;
			}
			set
			{
				this.connector = value;
			}
		}

		/// <summary>
		/// Gets or sets the mail subject.
		/// </summary>
		public virtual string Subject
		{
			get
			{
				return this.subject;
			}
			set
			{
				this.subject = value;
			}
		}

		/// <summary>
		/// Gets or sets the template content.
		/// </summary>
		/// <exception cref="ArgumentNullException">A null-reference or empty string was passed.</exception>
		public virtual string Contents
		{
			get
			{
				return this.contents;
			}
			set
			{
				this.contents = value;
			}
		}

		/// <summary>
		/// Gets or sets the data to be used with this template.
		/// </summary>
		/// <exception cref="ArgumentNullException">A null-reference was passed.</exception>
		public virtual MailData Data
		{
			get
			{
				return this.data;
			}
			set
			{
				this.data = value;
			}
		}

		/// <summary>
		/// Gets or sets the template parser.
		/// </summary>
		/// <exception cref="ArgumentNullException">A null-reference was passed.</exception>
		public TemplateParser Parser
		{
			get
			{
				return this.parser;
			}
			set
			{
				this.parser = value;
			}
		}

		/// <summary>
		/// Gets a list of target email addresses.
		/// </summary>
		public virtual IList<string> MailRecipients
		{
			get
			{
				return this.mailRecipients;
			}
		}

		/// <summary>
		/// Initialises this template.
		/// </summary>
		private void Initialise()
		{
			Contract.Requires(!String.IsNullOrWhiteSpace(this.contents));
			if (this.parser == null)
				this.parser = new TemplateParser();
			if (this.connector == null)
				this.connector = new HtmlMailConnector();
		}

		/// <summary>
		/// Processes this template by replacing all template variables with the data
		/// provided and sending the mail to the given recipient.
		/// </summary>
		/// <exception cref="ArgumentNullException">A null-reference or empty list was
		/// passed.</exception>
		/// <exception cref="InvalidOperationException">This operation cannot be performed because its instance 
		/// is in an invalid state.</exception>
		public virtual void Process()
		{
			this.Initialise();
			this.DataBind();
			if (this.abort)
			{
				this.abort = false;
				return;
			}

			if (this.data == null)
				throw new InvalidOperationException("No data defined, cannot proceed.");
			if (this.mailRecipients == null || this.mailRecipients.Count == 0)
				throw new InvalidOperationException("No email recipients defined, cannot proceed.");

			string mailContent = this.contents.Replace("\r", "");
			string mailSubject = this.subject;

			this.parser.Content = this.contents;
			this.parser.Parse();
			IEnumerable<TemplateVariable> variables = this.parser.Variables;
			this.parser.Content = this.subject;
			this.parser.Parse();
			variables = variables.Union(this.parser.Variables);

			foreach (TemplateVariable variable in variables)
			{
				MailDataItem dataItem = this.data[variable.Name];
				if (dataItem == null)
					continue;

				string newValue = "";

				if (dataItem is CompositeMailDataItem)
				{
					StringBuilder builder = new StringBuilder();

					CompositeMailDataItem compositeItem = dataItem as CompositeMailDataItem;
					foreach (List<AtomicMailDataItem> row in compositeItem)
					{
						if (variable.Style == DisplayStyle.TableRow)
							builder.Append("<tr>");
						else if (variable.Style == DisplayStyle.ListItem)
							builder.Append("<li>");

						foreach (AtomicMailDataItem cell in row)
						{
							string value = "";
							if (cell is LinkMailDataItem)
								value = "<a href=\"" + ((LinkMailDataItem)cell).Url + "\">" + cell.Value + "</a>";
							else
								value = cell.Value;

							if (variable.Style == DisplayStyle.TableRow)
							{
								builder.Append("<td>").Append(value).Append("</td>");
							}
							else
							{
								if (row.IndexOf(cell) > 0)
									builder.Append(", ").Append(value);
								else
									builder.Append(value);
							}
						}

						if (variable.Style == DisplayStyle.TableRow)
							builder.Append("</tr>");
						else if (variable.Style == DisplayStyle.ListItem)
							builder.Append("</li>");
					}

					newValue = builder.ToString();
				}
				else
				{
					AtomicMailDataItem atomicItem = dataItem as AtomicMailDataItem;

					if (atomicItem is LinkMailDataItem)
						newValue = "<a href=\"" + ((LinkMailDataItem)atomicItem).Url + "\">" + atomicItem.Value + "</a>";
					else
						newValue = atomicItem.Value;
				}

				mailContent = variable.Replace(mailContent, newValue);
				mailSubject = variable.Replace(mailSubject, newValue);
			}

			string currentRecipient = "";
			try
			{
				if (!this.connector.IsOpen)
					this.connector.Open();

				currentRecipient = String.Join("; ", this.mailRecipients);
				this.connector.Send(this.mailRecipients, mailSubject, mailContent);
			}
			catch (Exception ex)
			{
				throw new InvalidOperationException(String.Format("Could not send mail notification to recipient(s) '{0}'.", 
					currentRecipient), ex);
			}
		}

		/// <summary>
		/// Notifies the mail template processor to abort processing. This method can be used by data 
		/// binding methods to abort template processing.
		/// </summary>
		protected void Abort()
		{
			this.abort = true;
		}

		/// <summary>
		/// Disposes of this instance and releases managed resources.
		/// </summary>
		/// <param name="disposing">True if managed resources are to be released</param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (this.connector != null)
					this.connector.Dispose();
				this.connector = null;
			}
		}

		/// <see cref="IDisposable.Dispose()"/>
		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Performs the data binding before emails are sent.
		/// </summary>
		public abstract void DataBind();

	}

}